package com.feinno.fpa;

import com.feinno.database.ConnectionString;
import com.feinno.database.DataRow;
import com.feinno.database.DataTable;
import com.feinno.database.Database;
import com.feinno.database.spi.DatabaseManager;
import org.helium.framework.configuration.Environments;
import org.helium.framework.spi.Bootstrap;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.lang.System.*;

/**
 * Created by Lei Gao on 8/12/15.
 */
public class FpaGenerator {
	public static void main(String[] args) throws Exception {
		FpaGenerator generator = new FpaGenerator();
		Bootstrap.INSTANCE.getClass();
		ConnectionString connStr = ConnectionString.fromText(FpaDatabase.connStrText);
		generator.db = DatabaseManager.INSTANCE.getDatabase("FPA", connStr);
		generator.version = Environments.RUNTIME_VERSION.substring(8);

		generator.work();
	}

	Database db;
	String version;

	Map<String, ILF> ilfMap;
	Map<Integer, FpaFunction> funcs = new HashMap<>();
	Map<String, FpaFunction> funcsByName = new HashMap<>();
	FpaDictionary dict;

	public void work() throws SQLException {
		dict = new FpaDictionary(db);
		loadILFs();
		loadFunctions();
		loadDependencies();
		writeILFs();

		expandFunctions();
		// writeFunctions();
	}

	private void expandFunctions() {
		int n = 0;
		out.printf(">>> write Functions");
		for (FpaFunction func: funcs.values()) {
			for (FpaFunction ref: func.references) {
				FpaResult r = new FpaResult();
				r.module = func.module;
				r.subModule = func.packageName;
				r.function0 = func.className + "." + func.method + ">" + ref.className + "::" + ref.method;

				for (FpaParameter param: ref.parameters) {
					if (!r.attachIlf(ilfMap.get(param.type), dict)) {
						r.DETs0.add(param.name);
						r.DETs.add(dict.translate(param.name));
					}
				}
				if (!r.attachIlf(ilfMap.get(r.returnType), dict)) {
					r.DETs0.add(r.returnType);
					r.DETs.add(dict.translate(r.returnType));
				}

				r.function = dict.translate(func.className) + "的" + dict.translate(func.method) + "中处理" +
						dict.translate(ref.className) + dict.translate(ref.method);
				r.type = func.getType();

				r.insert(db, version);
				n++;
				if (n % 1000 == 0) {
					n++;
					out.print(".");
				}
				// out.printf("write function<%d> DETS=<%d> %s\n", n++, r.DETs.size(), r.function);
			}
		}
		out.printf(" count=%d DONE!!!\n", n);
	}

	private void writeFunctions() {
		final int[] n = {0};
		funcs.forEach((k, v) -> {
			FpaResult r = new FpaResult();
			r.type = v.getType();
			r.module = v.module;
			r.subModule = v.packageName;
			r.function0 = v.getKey();
			r.function = dict.translate(v.className + "的" + v.method);
			v.parameters.forEach(p -> {
				r.DETs0.add(p.name);
				r.DETs.add(p.name);
			});
			r.DETs.add(v.returnType.type);
			r.DETs0.add(v.returnType.type);
			r.insert(db, version);
			out.printf("write functions<%d>, %s\n", n[0]++, r.function);
			v.references.forEach(ref -> {
//				FpaResult r2 = r.clone();
//				r2.function0 = r.function + ref.className;
//				r2.insert(db, version);
			});
		});
	}

	private void loadFunctions() throws SQLException {
		int n = 0;
		for (DataRow row: db.executeTable("select * from function where module <> 'fx-group'").getRows()) {
			FpaFunction func = new FpaFunction();
			func.module = row.getString("module");
			String className = row.getString("className");
			func.packageName = FpaDictionary.extractPackageName(className);
			func.className = FpaDictionary.extractClassName(className);
			func.method = row.getString("method");

			func.id = row.getInt("id");
			// func.parameters = FpaDictionary.parseParameters(row.getString("parameter"));
			func.returnType = FpaDictionary.parseParameter(row.getString("return"));
			funcs.put(func.id, func);
			n++;
		}
		out.printf(">>> loadFunctions count=%d filling Parameters ", n);
		n = 0;
		for (FpaFunction func: funcs.values()) {
			func.fillParameters(db);
			n += func.parameters.size();
			//out.printf(">>> fillParameters<d%> for %s parameters=%d\n", n++, func.getKey(), func.parameters.size());
			if (n % 1000 == 0) {
				n++;
				out.print(".");
			}
		}
		out.printf("count=%d DONE!!\n", n);
	}

	private void loadDependencies() throws SQLException {
		int n = 0;
		int pn = 0;
		out.printf(">>> loading dependencies ");
		for (DataRow row: db.executeTable("select * from dependency").getRows()) {
			FpaFunction func = new FpaFunction();
			func.module = row.getString("module");
			String className = row.getString("className");
			func.packageName = FpaDictionary.extractPackageName(className);
			func.className = FpaDictionary.extractClassName(className);
			func.method = row.getString("method");

			func.id = row.getInt("id");
			func.dependencyId = row.getInt("functionId");
			func.parameters = FpaDictionary.parseParameters(row.getString("parameter"));
			func.returnType = FpaDictionary.parseParameter(row.getString("return"));

			FpaFunction ref = funcsByName.get(func.getKey());
			if (ref == null) {
				funcsByName.put(func.getKey(), func);
				ref = func;
			}
			if (func.dependencyId > 0) {
				FpaFunction parent = funcs.get(func.dependencyId);
				if (parent != null) {
					pn++;
					parent.references.add(ref);
				}
			}
			n++;
			if (n % 10000 == 0) {
				n++;
				out.print(".");
			}
		}
		out.printf(" dependencies=%d DONE!!!\n", n);

		out.printf(">>> fillParameters for Dependencies count=%d filling ", n);
		for (FpaFunction func: funcsByName.values()) {
			func.fillParameters(db);
			n += func.parameters.size();
			if (n % 1000 == 0) {
				n++;
				out.print(".");
			}
			//out.printf(">>> fillParameters<%d> for %s parameters=%d\n", n++, func.getKey(), func.parameters.size());
		}
		out.printf("parameters=%d DONE!\n", n);

//		out.printf(">>> Joining references ");
//		final int[] rn = {0};
//		funcsByName.forEach((k, v) -> {
//			if (v.dependencyId > 0) {
//				FpaFunction func = funcs.get(v.dependencyId);
//				if (func != null) {
//					rn[0]++;
//					func.references.add(func);
//					if (rn[0] % 1000 == 0) {
//						out.print(".");
//					}
//				}
//			}
//		});
//		out.printf(" references=%d DONE!!!\n", rn[0]);
	}


	private void writeILFs() throws SQLException {
		final int[] n = {0};
		out.printf(">>> writeILFs ");
		ilfMap.forEach((k, v) -> {
			FpaResult r = new FpaResult();
			r.module = v.module;
			r.subModule = v.packageName;
			r.type = FpaType.ILF;
			r.function = dict.translate(v.className);
			r.function0 = v.className;
			v.fields.forEach(field -> {
				r.DETs0.add(field);
				r.DETs.add(dict.translate(field));
			});

			r.insert(db, version);
			if ((n[0] % 1000) == 0) {
				out.print(".");
			}
			n[0]++;
		});
		out.printf(" count=%d DONE!!!\n", n[0]);
	}

	public void loadILFs() throws SQLException {
		DataTable table = db.executeTable("select * from entity");
		ilfMap = new HashMap<>();
		int n = 0;
		for (DataRow row: table.getRows()) {
			String module = row.getString("module");
			String packageName = FpaDictionary.extractPackageName(row.getString("className"));
			String className = FpaDictionary.extractClassName(row.getString("className"));
			String key = packageName + "." + className;
			ILF ilf = ilfMap.get(key);
			if (ilf == null) {
				ilf = new ILF();
				ilf.module = module;
				ilf.className = className;
				ilf.packageName = FpaDictionary.extractPackageName(row.getString("className"));;
				ilfMap.put(key, ilf);
				// out.printf("load ILF<%d>: %s \r\n", n++, ilf.className);
			}
			String field = row.getString("field");
			ilf.fields.add(field);
		}
	}
}
